
#include <sstream>
#include <iostream>
#include "Scanner.h"


namespace mjc {

	const wchar_t Scanner::eol = L'\n';
	const wchar_t Scanner::eof = L'\uffff';

	const std::tr1::wregex Scanner::sRegex(L"[a-zA-Z]");
	const std::tr1::wregex Scanner::nRegex(L"[0-9]");
	const std::tr1::wregex Scanner::snRegex(L"[a-zA-Z0-9]");

	Scanner::Scanner(std::wstreambuf* inbuf):
		ch(0),
		column(0),
		line(0),
		position(0),
		lexeme(),
		in(inbuf)
	{}

	Scanner::Scanner(mjc::Scanner &other):
		ch(other.ch),
		column(other.column),
		line(other.line),
		position(other.position),
		lexeme(other.lexeme),
		in(other.in.rdbuf())
	{
		in.seekg(other.in.tellg(), std::ios_base::beg);
	}
	

	void Scanner::NextChar()
	{
		in.get(ch);

		if(in.eof())
		{
			ch = eof;
			return;
		}

		++column;
		++position;

		if(ch == eol)
		{
			++line;
			column = 0;
		}
	}

	void Scanner::ReadName(Token& token)
	{
		std::wstringstream ss;

		while(std::tr1::regex_search(&ch, &ch+1, snRegex))
		{
			ss << ch;
			NextChar();
		}

		
		if(ss.str().size() > 0)
		{
			//if anything was read go back to previous char
			in.seekg(-1, std::ios_base::cur);
		
			token.strValue = ss.str();
			TokenType::Type type;

			if(TokenData::GetKeywordToken(token.strValue, type))
				token.type = type;
			else
				token.type = TokenType::T_IDENT;
		}
	}

	void Scanner::ReadNumber(Token& token)
	{
		std::wstringstream ss;

		while(std::tr1::regex_search(&ch, &ch+1, nRegex))
		{
			ss << ch;
			NextChar();
		}

		if(ss.str().size() > 0)
		{
			//if anything was read go back to previous char
			in.seekg(-1, std::ios_base::cur);

			if((ss >> token.value).fail())
			{
				token.type = TokenType::T_NONE;
				std::cout << __FUNCTION__ << L": Input value is not an integer" << std::endl;
			}
			else
			{
				token.strValue = ss.str();//store numeric value to string
				token.type = TokenType::T_NUMBER;
			}
		}
	}

	Token Scanner::NextToken()
	{
		//Token to be setup and returned
		Token token;
		token.line = line;
		token.column = column;
		token.type = TokenType::T_NONE;

		//get next character, skip blanks, tabs, eols...
		while(ch <= L' ')
			NextChar();

		//reached end of file
		if(ch == eof)
		{
			token.type = TokenType::T_EOF;
			return token;
		}

		//check whether its a name or a number using regex
		if(std::tr1::regex_search(&ch, &ch+1, sRegex))
		{
			ReadName(token);
			NextChar();
		}
		else if(std::tr1::regex_search(&ch, &ch+1, nRegex))
		{
			ReadNumber(token);
			NextChar();
		}
		else //whatever else
		{
			switch(ch)
			{
			case L'/':
				NextChar();

				if(ch == L'/')//comment
				{
					do
					{
						NextChar();
					}
					while(ch != L'\n'  && ch != eof);//to the end of the line

					token = NextToken();
				}
				else
					token.type = TokenType::T_SLASH;
			break;

			//TokenType::T_CHAR
			case L'\'':
			{
				NextChar();

				if(std::tr1::regex_search(&ch, &ch+1, snRegex) || ch == ' ')
				{
					wchar_t c = ch;
					NextChar();

					if(ch == L'\'')
					{
						token.type = TokenType::T_CHAR;
						token.value = static_cast<int>(c);
						token.strValue = std::wstring(&c, 1);
						NextChar();
					}
					
				}
				else if(ch == L'\\')//control chars \r,\n,\t
				{
					wchar_t c = L'x';
					NextChar();

					switch(ch)
					{
						case L'r': c = L'\r'; break;
						case L'n': c = L'\n'; break;
						case L't': c = L'\t'; break;
					}

					if(c != L'x')//c was modified, then its a control char
					{
						NextChar();

						if(ch == L'\'')
						{
							token.type = TokenType::T_CHAR;
							token.value = static_cast<int>(c);
							token.strValue = std::wstring(&c, 1);
							NextChar();
						}
					}
				}
			}
			break;

			case L'+':
				token.type = TokenType::T_PLUS;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'-':
				token.type = TokenType::T_MINUS;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'*':
				token.type = TokenType::T_TIMES;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'%':
				token.type = TokenType::T_REM;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'=':
				NextChar();

				if(ch == L'=')
				{
					token.type = TokenType::T_EQL;
					NextChar();
				}
				else
					token.type = TokenType::T_ASSIGN;

				TokenData::GetTokenName(token.type, token.strValue);
				
			break;

			case L'!':
				NextChar();

				if(ch == L'=')
				{
					token.type = TokenType::T_NEQ;
					TokenData::GetTokenName(token.type, token.strValue);
					NextChar();
				}
			break;

			case L'<':
				NextChar();

				if(ch == L'=')
				{
					token.type = TokenType::T_LEQ;
					NextChar();
				}
				else
					token.type = TokenType::T_LSS;

				TokenData::GetTokenName(token.type, token.strValue);
			break;

			case L'>':
				NextChar();

				if(ch == L'=')
				{
					token.type = TokenType::T_GEQ;
					NextChar();
				}
				else
					token.type = TokenType::T_GTR;

				TokenData::GetTokenName(token.type, token.strValue);
			break;

			case L';':
				token.type = TokenType::T_SEMICOLON;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L',':
				token.type = TokenType::T_COMMA;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'.':
				token.type = TokenType::T_PERIOD;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'(':
				token.type = TokenType::T_LPAR;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L')':
				token.type = TokenType::T_RPAR;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'[':
				token.type = TokenType::T_LBRACK;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L']':
				token.type = TokenType::T_RBRACK;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'{':
				token.type = TokenType::T_LBRACE;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			case L'}':
				token.type = TokenType::T_RBRACE;
				TokenData::GetTokenName(token.type, token.strValue);
				NextChar();
			break;

			default:
				token.type = TokenType::T_NONE;
				//std::cout << __FUNCTION__ << L": undefined token type" << std::endl;
			}
		}

		return token;
	}
}